package org.haredot.context;


import org.haredot.annotation.Resource;
import org.haredot.annotation.Transactional;
import org.haredot.factory.BeanFactory;
import org.haredot.proxy.TranscationProxy;
import org.haredot.utils.ClassUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


public interface ApplicationContext extends BeanFactory {
    /**
     * 一级缓存、 用来存储 单例 bean 对象 (成品)  scope="singleton"
     */
    public static final Map<String, Object> ONE_CACHE_MAP = new ConcurrentHashMap<>();
    /**
     * 二级缓存、用来存放 类对象 （半成品） scope="prototype"
     */
    public static final Map<String, Class<?>> TWO_CACHE_MAP = new ConcurrentHashMap<>();

    @Override
    default Object getBean(String beanId)  {
        // 从 一级缓存中查找 对应的 对象
        Object o = ONE_CACHE_MAP.get(beanId);
        // 如果 从 一级缓存中 查找到对象，则直接返回该对象
        if (o != null) return o ;
        // 如果 一级缓存中 没有找到该对象， 从二级缓存中 尝试 获取该 类的 类对象
        Class<?> aClass = TWO_CACHE_MAP.get(beanId);
        // 如果 二级缓存 也找不到 想要的数据，则代表 容器中没有该数据
        if (aClass == null) return null ;
        // 如果 二级 缓存 找到 了 类对象 ， 使用 无参构造方法 创建对象
        try {
            o = aClass.getConstructor().newInstance();
            // 注入@Resource注解注释的属性值
            weaveObj(o);
            return getTransactionProxyBean(o) ;
        }catch (Exception e) {
            throw new ExceptionInInitializerError(aClass +" 类中没有提供 公开无参构造方法") ;
        }
    }

    @Override
    default <T> T getBean(String beanId, Class<T> clazz) {
        Object bean = getBean(beanId);
        return (T) bean ;
    }

    @Override
    default <T> T getBean(Class<T> clazz) {
        // 从 一级缓存中查找数据
        List<Object> objOrClass = findObjOrClass(clazz, ONE_CACHE_MAP);

        // 从 二级缓存中查找数据
        List<Object> objOrClass1 = findObjOrClass(clazz, TWO_CACHE_MAP);

        final int count = objOrClass.size() + objOrClass1.size();
        // 两个缓存均为找到数据
        if(count == 0) return null ;

        // 两个缓存找到的数据超过 1个
        if(count > 1) {
            objOrClass.addAll(objOrClass1);
            throw new ExceptionInInitializerError(clazz +" 期待从容器中找到1个对象、实际找到" + count + "个、分别是" + objOrClass) ;
        }
        // 如果 从一级缓存中找到的数据不为空，则代表找到了唯一的1个
        if (!objOrClass.isEmpty()) {
            return (T) getTransactionProxyBean(objOrClass.get(0)) ;
        }
        // 从 二级缓存中 取到 唯一获得的对象、并转换成 类对象
        Class<?> aClass = (Class<?>)objOrClass1.get(0);
        // 如果 二级 缓存 找到 了 类对象 ， 使用 无参构造方法 创建对象
        try {
            Object obj = aClass.getConstructor().newInstance();
            weaveObj(obj);
            return (T) getTransactionProxyBean(obj) ;
        }catch (Exception e) {
            throw new ExceptionInInitializerError(aClass +" 类中没有提供 公开无参构造方法") ;
        }
    }

    /**
     * 给 添加了 @Transactional注解的对象添加事务代理
     * @param obj
     * @return
     */
    private Object getTransactionProxyBean(Object obj) {
        Class<?> clazz = obj.getClass();
        // 判断 传入的对象 所在的类上 是否 由 @Transactional 注解
        if (clazz.isAnnotationPresent(Transactional.class)) {
            // 添加 事务代理
            return TranscationProxy.getProxy(obj) ;
        }
        return obj ;
    }

    /**
     * 给 二级缓存管理的 对象 织入 属性值
     * @param obj : 要织入数据的对象
     */
    default void weaveObj(Object obj) {
        // 获取 obj 对象的 类对象
        Class<?> aClass = obj.getClass();
        // 找到 aClass中包含@Resource 注解的属性
        // 获取 obj 对应的 所有属性
        List<Field> allFields = ClassUtils.getAllFields(aClass);
        // 获取 field 上 包含 @Resource 注解的属性
        allFields.stream().filter(f -> f.isAnnotationPresent(Resource.class)).forEach(f -> {
            // 获取 @Resource注解
            Resource annotation = f.getAnnotation(Resource.class);
            // 获取 name 成员
            String name = annotation.name();

            // 如果 name 为空字符串，用属性名作为 name的值
            String beanId = "".equals(name) ? f.getName() : name ;
            try {
                // 根据 beanId 查找 要注入的对象
                Object bean = getBean(beanId);

                if (bean == null) {
                    // 获取 当前属性的 类型
                    Class<?> type = f.getType();
                    // 根据类型查找 对象
                    bean = getBean(type);

                    if (bean == null) throw new ExceptionInInitializerError("beanId=" + beanId + "的对象在容器中找不到");
                }

                if (!f.trySetAccessible()) f.setAccessible(true);
                // 如果找到了，则将 bean 放到 属性中即可
                f.set(obj, bean);

            } catch (Exception e) {
                e.printStackTrace();
                throw new ExceptionInInitializerError("beanId=" + beanId + "的对象在容器中获取失败、失败的原因是" + e.getMessage());
            }
        });
    }

    /**
     * 从缓存中查找数据
     * @param clazz
     * @return
     */
    private List<Object> findObjOrClass(Class<?> clazz, final Map<String, ?> cacheMap) {
        // 1. 获取 一级缓存 中所有的对象
        Collection<?> cacheValues = cacheMap.values();
        // 定义一个容器，用来存储 找到 的 类型
        List<Object> list = new ArrayList<>() ;
        // 遍历 一级缓存中的所有数据、找到 clazz 类型的对象
        for(Object obj : cacheValues) {
            // 定义一个副本、用来指向 类对象
            Object $obj = obj ;
            if (!(obj instanceof Class)) $obj = obj.getClass() ;
            // 此时 $obj 一定是 Class
            Class aclazz = (Class)$obj ;
            // 如果 类对象 相等，则将 obj 放入到 集合容器中
            if (aclazz == clazz) {
                list.add(obj);
                continue ;
            }
            // 获取
            List<Class<?>> allSuperClasses = ClassUtils.getAllSuperClasses(aclazz);
            // 如果 当前对象所在类的 所有父类中 包含 clazz, 说明 当前对象 是我们要找的对象
            if (allSuperClasses.contains(clazz))  {
                list.add(obj);
            }else{
                // 查找 obj 对象所在的类 实现的所有接口
                List<Class<?>> allSuperInterfaces = ClassUtils.getAllSuperInterfaces(aclazz);
                // 如果 父接口中 包含 clazz, 说明 该对象 仍旧是我们要找的对象
                if (allSuperInterfaces.contains(clazz)) list.add(obj);
            }
        }
        return list ;
    }
}
